package com.behelpful.common.swagger.config;

import com.behelpful.common.core.predicate.IPredicates;
import com.behelpful.common.core.result.FailedResult;
import com.behelpful.common.core.utils.ICollUtil;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.*;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author Alay
 * @date 2021-08-21 21:47
 * @since
 */
@Slf4j
@Configuration
@EnableOpenApi
@EnableKnife4j
@AllArgsConstructor
@EnableConfigurationProperties(value = SwaggerProperties.class)
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
public class SwaggerConfig {
    /**
     * 默认的排除路径，排除Spring Boot默认的错误处理路径和端点
     */
    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");

    private static final String BASE_PATH = "/**";
    /**
     * 属性文件中读取配置的属性类
     */
    private final SwaggerProperties swaggerProperties;


    @Bean
    public Docket docket() {
        // Swagger 3.0 版本
        Docket docket = new Docket(DocumentationType.OAS_30);
        docket
                // .groupName(swaggerProperties.getGroupName())
                .host(swaggerProperties.getHost())
                // 是否开启文档生成
                .enable(swaggerProperties.getEnabled())
                // Api 信息配置
                .apiInfo(this.apiInfo(swaggerProperties))
                // 全局请求参数配置
                .globalRequestParameters(this.globalRequestParameters())

                // 全局通用响应信息
                .globalResponses(HttpMethod.GET, this.globalResponses())
                .globalResponses(HttpMethod.POST, this.globalResponses())
                .globalResponses(HttpMethod.PUT, this.globalResponses())
                .globalResponses(HttpMethod.DELETE, this.globalResponses())

                .select()
                // Api 的规则,断言（通过什么方式引入APi 这里是通过包扫描）
                // .apis(this.apiSelector())
                // 这个是通过 Api注解
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                // 路径匹配
                .paths(this.pathSelector())
                .build()

                // 授权相关
                .securitySchemes(this.securitySchema())
                .securityContexts(this.securityContext())
                .pathMapping("/");

        log.info("Behelpful Swagger | 配置完成");
        return docket;
    }


    /**
     * Api 解析的包路径选择器
     *
     * @return
     */
    private Predicate<RequestHandler> apiSelector() {
        // 设定扫描那个包（及子包）中的注解
        Predicate<RequestHandler> handlerPredicate = RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage());
        return handlerPredicate;
    }

    /**
     * 路径选择器
     *
     * @return
     */
    private Predicate<String> pathSelector() {
        // 基础路径处理
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add(BASE_PATH);
        }
        // 排除的路径配置
        if (swaggerProperties.getExcludePath().isEmpty()) {
            swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
        }

        // 基础路径地址转换为断言
        List<Predicate<String>> basePath = swaggerProperties.getBasePath().stream()
                .map(path -> PathSelectors.ant(path))
                .collect(Collectors.toList());

        // 排除的路径转换为断言
        List<Predicate<String>> excludePath = swaggerProperties.getExcludePath().stream()
                .map(path -> PathSelectors.ant(path))
                .collect(Collectors.toList());

        // 路径返回约束
        Predicate<String> pathPredicate = IPredicates.and(
                IPredicates.not(IPredicates.or(excludePath)),
                IPredicates.or(basePath)
        );
        return pathPredicate;
    }

    /**
     * 文档上下文主体描述属性信息对象构建
     *
     * @param swaggerProperties
     * @return
     */
    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .license(swaggerProperties.getLicense())
                .licenseUrl(swaggerProperties.getLicenseUrl())
                .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
                .contact(
                        new Contact(
                                swaggerProperties.getContact().getName(),
                                swaggerProperties.getContact().getUrl(),
                                swaggerProperties.getContact().getEmail()
                        )
                )
                .version(swaggerProperties.getVersion())
                .build();
    }


    /**
     * 全局请求参数处理
     * 如： 灰度版本号
     *
     * @return
     */
    private List<RequestParameter> globalRequestParameters() {
        //
        List<RequestParameter> globalParameters = new ArrayList<>();

        // 版本请求头处理
        RequestParameter versionParameter = new RequestParameterBuilder()
                .name("VERSION")
                .description("灰度路由版本信息")
                .in(ParameterType.HEADER)
                .required(false)
                .build();
        globalParameters.add(versionParameter);

        // 分组
        /*Example example = new Example(swaggerProperties.getGroupName(), swaggerProperties.getGroupName(), "自定义分组",
                swaggerProperties.getGroupName(), swaggerProperties.getGroupName(),
                MediaType.APPLICATION_FORM_URLENCODED_VALUE);
        RequestParameter groupParameter = new RequestParameterBuilder()
                .name("group")
                .description("组名")
                .in(ParameterType.QUERY)
                .required(false)
                .example(example)
                .build();
        globalParameters.add(groupParameter);*/

        return globalParameters;
    }

    /**
     * 返回信息设置（这里只是示例）
     *
     * @return
     */
    private List<Response> globalResponses() {
        List<Response> responses = new ArrayList<>();
        responses.add(
                new ResponseBuilder()
                        .code(FailedResult.FAILED.getCode())
                        .description(FailedResult.FAILED.getMessage()).build()
        );
        return responses;
    }

    /**
     * 权限认证
     *
     * @return
     */
    private List<SecurityScheme> securitySchema() {
        List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
        swaggerProperties.getAuthorization()
                .getAuthorizationScopeList()
                .forEach(authorizationScope -> authorizationScopeList.add(
                        new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription())));

        List<GrantType> grantTypes = new ArrayList<>();
        swaggerProperties.getAuthorization()
                .getTokenUrlList()
                // 权限认证调用服务的 Api,如：http://xxx/xxx/login
                .forEach(tokenUrl -> grantTypes.add(new ResourceOwnerPasswordCredentialsGrant(tokenUrl)));
        OAuth oAuth = new OAuth(swaggerProperties.getAuthorization().getName(), authorizationScopeList, grantTypes);
        return ICollUtil.asList(oAuth);
    }


    /**
     * 另一种权鉴认证
     *
     * @return
     */
    /*private List<SecurityScheme> securitySchema() {
        // ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");
        ApiKey apiKey = new ApiKey("Authorization-Token", "Authorization-Token", "header");
        return ICollUtil.asList(apiKey);
    }*/


    /**
     * 配置默认的全局鉴权策略的开关，通过正则表达式进行匹配；默认匹配所有URL
     *
     * @return
     */
    private List<SecurityContext> securityContext() {
        SecurityContext securityContext = new SecurityContext(
                this.defaultAuth(),
                PathSelectors.regex(swaggerProperties.getAuthorization().getAuthRegex()),
                method -> true,
                selector -> true
        );
        return ICollUtil.asList(securityContext);
    }

    /**
     * 默认的全局鉴权策略
     *
     * @return
     */
    private List<SecurityReference> defaultAuth() {
        List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
        swaggerProperties.getAuthorization()
                .getAuthorizationScopeList()
                .forEach(authorizationScope -> authorizationScopeList.add(
                        new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription()))
                );

        AuthorizationScope[] authorizationScopes = new AuthorizationScope[authorizationScopeList.size()];

        authorizationScopes = authorizationScopeList.toArray(authorizationScopes);

        SecurityReference securityReference = new SecurityReference(
                swaggerProperties.getAuthorization().getName(),
                authorizationScopes
        );
        return ICollUtil.asList(securityReference);
    }
}